/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.util;

import com.aduib.boot.common.exception.JwtTokenExpiredException;
import com.aduib.boot.common.security.RSAKeyUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyPair;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

/**
 * @author ZZH
 * @version 1.0.0
 */
public class JwtUtils {

  // Token过期时间30分钟（用户登录过期时间是此时间的两倍，以token在reids缓存时间为准）
  public static final long EXPIRE_TIME = 30 * 60 * 1000;
  // 默认对称加密方式
  private static final SignatureAlgorithm ALGORITHM_HS512 = SignatureAlgorithm.HS512;
  // 默认对称加密key
  public static final SecretKey SECRET_KEY = createSecretKey(ALGORITHM_HS512);
  // 默认非对称加密方式
  private static final SignatureAlgorithm ALGORITHM_RS512 = SignatureAlgorithm.RS512;
  // 默认非对称加密key
  public static final KeyPair KEY_PAIR = createAsymmetricKeys(ALGORITHM_RS512);
  // 默认token发行人
  private static final String issUser = "aduib-jwt";

  /**
   * 创建 jwt token
   *
   * @param issUser token发行人
   * @param expTime 过期时间
   * @param subject 主题
   * @param audience 用户
   * @param issuedAt token创建时间
   * @param claims
   * @param key 加密key
   * @return
   */
  public static String createSign(
      String issUser,
      Date expTime,
      String subject,
      String audience,
      Date issuedAt,
      Claims claims,
      Key key) {
    claims.setIssuer(issUser);
    claims.setExpiration(expTime);
    claims.setSubject(subject);
    claims.setIssuedAt(issuedAt);
    claims.setIssuer(issUser);
    return Jwts.builder()
        .setIssuer(issUser)
        .setExpiration(expTime)
        .setSubject(subject)
        .setAudience(audience)
        .setIssuedAt(issuedAt)
        .setClaims(claims)
        .signWith(key)
        .compact();
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param expTime
   * @param key
   * @return
   */
  public static String createSign(
      String username, String roleList, String password, Date expTime, Key key) {
    Claims claims = Jwts.claims();
    claims.put("username", username);
    claims.put("roleList", roleList);
    claims.put("password", password);
    return createSign(issUser, expTime, roleList, username, new Date(), claims, key);
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param expTime
   * @param isEnableSecretKey
   * @return
   */
  public static String createSign(
      String username, String roleList, String password, Date expTime, boolean isEnableSecretKey) {
    Key key;
    if (isEnableSecretKey) {
      key = SECRET_KEY;
      return createSign(username, roleList, password, expTime, key);
    } else {
      key = KEY_PAIR.getPrivate();
      return createSign(username, roleList, password, expTime, key);
    }
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param expTime
   * @param isEnableSecretKey
   * @param algorithm
   * @return
   */
  public static String createSign(
      String username,
      String roleList,
      String password,
      Date expTime,
      boolean isEnableSecretKey,
      SignatureAlgorithm algorithm) {
    Key key;
    KeyPair keyPair;
    if (isEnableSecretKey) {
      key = createSecretKey(algorithm);
      return createSign(username, roleList, password, expTime, key);
    } else {
      keyPair = createAsymmetricKeys(algorithm);
      key = keyPair.getPrivate();
      return createSign(username, roleList, password, expTime, key)
          + ";"
          + Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())
          + ";"
          + Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
    }
  }

  /**
   * 验证 jwt token
   *
   * @param issUser
   * @param subject
   * @param audience
   * @param extraClaims
   * @param token
   * @param signKey
   * @return
   */
  public static boolean verifySign(
      String issUser,
      String subject,
      String audience,
      Claims extraClaims,
      String token,
      Key signKey) {
    JwtParser jwtParser =
        Jwts.parserBuilder()
            .requireIssuer(issUser)
            .requireSubject(subject)
            .requireAudience(audience)
            .setSigningKey(signKey)
            .build();

    boolean verifySign = false;
    try {
      Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
      Claims claims = claimsJws.getBody();
      if (claims.getIssuer().equals(issUser)) {
        verifySign = true;
      }
      if (claims.getSubject().equals(subject)) {
        verifySign = true;
      }
      if (claims.getAudience().equals(audience)) {
        verifySign = true;
      }
      if (!claims.isEmpty()) {
        for (Map.Entry<String, Object> entry : extraClaims.entrySet()) {
          String x = entry.getKey();
          Object y = entry.getValue();
          if (claims.containsKey(x) && claims.get(x).equals(y)) {
            verifySign = true;
          } else {
            break;
          }
        }
      }
    } catch (ExpiredJwtException e) {
      throw new JwtTokenExpiredException(e.getMessage(), token);
    }
    return verifySign;
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param signKey
   * @param token
   * @return
   */
  public static boolean verifySign(
      String username, String roleList, String password, Key signKey, String token) {
    Claims claims = Jwts.claims();
    claims.put("username", username);
    claims.put("roleList", roleList);
    claims.put("password", password);
    return verifySign(issUser, roleList, username, claims, token, signKey);
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param token
   * @param isEnableSecretKey
   * @return
   */
  public static boolean verifySign(
      String username, String roleList, String password, String token, boolean isEnableSecretKey) {
    Key key;
    if (isEnableSecretKey) {
      key = SECRET_KEY;
      return verifySign(username, roleList, password, key, token);
    } else {
      key = KEY_PAIR.getPublic();
      return verifySign(username, roleList, password, key, token);
    }
  }

  /**
   * @param username
   * @param roleList
   * @param password
   * @param signKey
   * @param token
   * @param isEnableSecretKey
   * @return
   */
  public static boolean verifySign(
      String username,
      String roleList,
      String password,
      String signKey,
      String token,
      boolean isEnableSecretKey) {
    Key key;
    if (isEnableSecretKey) {
      key = Keys.hmacShaKeyFor(signKey.getBytes(StandardCharsets.UTF_8));
      return verifySign(username, roleList, password, key, token);
    } else {
      key = RSAKeyUtils.getPublicKey(signKey);
      return verifySign(username, roleList, password, key, token);
    }
  }

  /**
   * 创建 jwt 对称加密key
   *
   * @param algorithm 加密方式
   * @return
   */
  public static SecretKey createSecretKey(SignatureAlgorithm algorithm) {
    return Keys.secretKeyFor(algorithm);
  }

  /**
   * 创建 jwt 非对称加密key
   *
   * @param algorithm 加密方式
   * @return
   */
  public static KeyPair createAsymmetricKeys(SignatureAlgorithm algorithm) {
    return Keys.keyPairFor(algorithm);
  }
}
